/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package practica1.preferences;

import java.io.*;
import java.util.ArrayList;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import practica1.Practica1;


/**
 * Clase de instancia que permite manejar las preferencias del programa
 * a través de un fichero XML
 * @author Miguel González - Ceura
 */
public class PreferencesXML {
    private static PreferencesXML INSTANCE = null;
    private ArrayList<PreferencesListener> preferencesListener;
    
    private final String ficheroPreferences;
    
    //XML
    private Document xmlDoc = null;
    private Element elementoCabecera = null;
    
    /**
     * Constructor privado de las preferencias
     */
    private PreferencesXML() {
        preferencesListener = new ArrayList<PreferencesListener>();
        
        ficheroPreferences = System.getProperty("user.home") +
                "/.practica1/preferences.xml";
        
        
        //Construimos el DOM con las preferencias
        DocumentBuilderFactory dbFactory;
        DocumentBuilder docBuilder;
        
        try
        {
            dbFactory = DocumentBuilderFactory.newInstance();
            docBuilder = dbFactory.newDocumentBuilder();
            
            //Si se agrega al fichero y este existe tenemos que cargar primero el XML en memoria
            File preferencesFile = new File(ficheroPreferences);

            if(preferencesFile.exists()) {
                xmlDoc  = docBuilder.parse(preferencesFile);
                
                elementoCabecera = xmlDoc.getDocumentElement();
            } else {
                //Creamos un documento vacío
                xmlDoc = docBuilder.newDocument();
                 //Creamos el elemento padre
                elementoCabecera = xmlDoc.createElement("preferences");
                //Lo agregamos al documento
                xmlDoc.appendChild(elementoCabecera);
            }
        }
        catch (Exception ex)
        {
            Practica1.log.error("Error", ex);
        }
    }
    
    /**
     * Devuelve la instancia de la clase para acceder a las preferencias del programa
     * @return PreferencesXML
     */
    public synchronized static PreferencesXML getInstance() {
        if(INSTANCE == null) {
            INSTANCE = new PreferencesXML();
        }
        
        return INSTANCE;
    }
    
    /**
     * Permite suscribirse a las notificaciones de la clase
     * @param l PreferencesListener
     */
    public void addPreferencesListener(PreferencesListener l) {
        if(l != null) {
            if(!preferencesListener.contains(l)) {
                preferencesListener.add(l);
            }
        }
    }
    
    /**
     * Permite desuscribirse a las notificaciones de la clase
     * @param l PreferencesListener
     */
    public void removePreferencesListener(PreferencesListener l) {
        if(l != null) {
            preferencesListener.remove(l);
        }
    }
    
    /**
     * Establece un valor para ser guardado en las preferencias del programa
     * @param clase Clase que guarda y recuperará la preferencia
     * @param clave Clave de la preferencia
     * @param valor Valor de la preferencia
     */
    public void setValue(Object clase, String clave, String valor) {
        boolean encontradaPropiedad = false;
        
        //Buscamos las propiedades de la clase
        NodeList listaNodosClases = elementoCabecera.getElementsByTagName(clase.getClass().getName());
        
        //Si no está la clase la agregamos
        if(listaNodosClases.getLength() == 0) {
            Element elementClase = xmlDoc.createElement(clase.getClass().getName());
            elementoCabecera.appendChild(elementClase);
            listaNodosClases = elementoCabecera.getElementsByTagName(clase.getClass().getName());
        } 
        
        //Obtenemos el nodo de la clase
        Node nodoClase = listaNodosClases.item(0);

        //Obtenemos las propiedades de la clase
        NodeList listaValores = nodoClase.getChildNodes();
        //Las recorremos
        for(int j=0; j<listaValores.getLength() && !encontradaPropiedad; j++) {
            Node nodoValor = listaValores.item(j);
            if(nodoValor instanceof Element) {
                String nombrePropiedad = nodoValor.getNodeName();
                String valorPropiedad = nodoValor.getTextContent();
                //Si hemos encontrado la propiedad
                if(nombrePropiedad.equals(clave)) {
                    nodoValor.setTextContent(valor);
                    encontradaPropiedad = true;
                }
            }
        } //Fin recorrer las propiedades
        
        
        //Si no hemos encontrado la propiedad creamos una nueva
        if(!encontradaPropiedad) {
            //Creamos un elemento con la clave-valor
            Element propiedad = xmlDoc.createElement(clave);
            propiedad.setTextContent(valor);
            //Agregamos la propiedad
            nodoClase.appendChild(propiedad);
        }
    }
    
    /**
     * Método que permite obtener una preferencia del programa
     * @param clase Clase que ha guardado la preferencia
     * @param clave Clave de la preferencia
     * @param valorDefecto Valor por defecto por si la preferencia no existe
     * @return String value
     */
    public String getValue(Object clase, String clave, String valorDefecto) {
        String valor = "";
        //Buscamos las propiedades de la clase
        NodeList listaNodosClases = elementoCabecera.getElementsByTagName(clase.getClass().getName());
        //Recorremos las propiedades
        for(int i=0;i<listaNodosClases.getLength();i++){  
            Node nodo = listaNodosClases.item(i);
            //Comprobamos que es un elemento, no tiene porque serlo
            if (nodo instanceof Element) {
                //Obtenemos las propiedades de la clase
                NodeList listaValores = nodo.getChildNodes();
                //Las recorremos
                for(int j=0; j<listaValores.getLength(); j++) {
                    Node nodoValor = listaValores.item(j);
                    if(nodoValor instanceof Element) {
                        String nombrePropiedad = nodoValor.getNodeName();
                        String valorPropiedad = nodoValor.getTextContent();
                        if(nombrePropiedad.equals(clave)) {
                            valor = valorPropiedad;
                        }
                    }
                }
                
            }  
        }
        
        if(valor.isEmpty()) {
            return valorDefecto;
        } else {
            return valor;
        }
    }
    
    /**
     * Guarda las preferencias a disco duro
     */
    public void savePreferences() {
        //Lo primero es guardar todas las preferences de los manejadores
        for(PreferencesListener l : preferencesListener) {
            l.savePreferences();
        }
        
        OutputStream out = null;
        OutputStreamWriter osw = null;
        StreamResult result;
        DOMSource source;
        Transformer transformer;
        TransformerFactory transformerFactory;
        
        try {
            File fichPreferencesCarpeta = new File(ficheroPreferences).
                    getParentFile();
            if(!fichPreferencesCarpeta.exists()) {
                fichPreferencesCarpeta.mkdirs();
            }
            
            transformerFactory = TransformerFactory.newInstance();
            transformer = transformerFactory.newTransformer();
            transformer.setOutputProperty(OutputKeys.INDENT, "yes");
            transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
            
            source = new DOMSource(xmlDoc);
            out = new FileOutputStream(ficheroPreferences, false);
            osw = new OutputStreamWriter(out, "UTF-8");
            result = new StreamResult(osw);
            
            transformer.transform(source, result);
        } catch (TransformerException ex) {
            Practica1.log.error("Error", ex);
        } catch(IOException ex) {
            Practica1.log.error("Error", ex);
        } finally {
            try {
                if(osw != null) {
                    osw.close();
                }
                if(out != null) {
                    out.close();
                }
            } catch(IOException ex) {
                Practica1.log.error("Error", ex);
            }
        }
    }
}
